#pragma once

// #include "oj_model.hpp"
#include "oj_model2.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"
#include "../comm/log.hpp"

#include <vector>
#include <mutex>
#include <memory>
#include <fstream>
#include <algorithm>
#include <jsoncpp/json/json.h>

#include "../message_queue/src/mqclient/mq_connection.hpp"

#include <sw/redis++/redis++.h>

namespace ns_control
{
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    // 服务主机
    struct Machine
    {
        std::string ip;   // 主机IP
        int port;         // 编译服务的port
        uint64_t load;    // 编译服务的负载
        std::mutex mutex; // 保证load的线程安全

        Machine() {}
        Machine(std::string cip, int cport) : ip(cip), port(cport), load(0) {}
        ~Machine() {}

        // 获取负载值
        uint64_t Load()
        {
            std::unique_lock<std::mutex> _lock(mutex);
            return load;
        }

        void reset()
        {
            std::unique_lock<std::mutex> _lock(mutex);
            load = 0;
        }

        // 提高主机负载
        void IncLoad()
        {
            std::unique_lock<std::mutex> _lock(mutex);
            ++load;
        }
        // 降低主机负载
        void DecLoad()
        {
            std::unique_lock<std::mutex> _lock(mutex);
            --load;
        }
    };

    // 负载均衡模块
    using MachinePtr = std::shared_ptr<Machine>;
    const std::string service_machine = "./conf/service_machine.conf";
    class LoadBlance
    {
    private:
        // 管理所有提供编译服务的主机,每个主机都有一个唯一的下标 ,我们把下标作为主机的唯一标识存放进online和offline中
        std::vector<MachinePtr> _machines;
        std::vector<int> online;
        std::vector<int> offline;
        std::mutex _mutex;

    public:
        LoadBlance()
        {
            assert(LoadConf(service_machine));
            // LOG(INFO) << "加载 " << service_machine << " 成功,当前在线主机数量 : " << online.size() << "\n";
            ILOG("加载 %s 成功, 当前在线主机数量: %d", service_machine.c_str(), online.size());
        }

    private:
        bool LoadConf(const std::string &machine_conf)
        {
            std::fstream in(machine_conf);
            if (!in.is_open())
            {
                // ns_log::LOG(FATAL) << " 加载: " << machine_conf << " 失败" << "\n";
                ELOG("加载 %s 失败", machine_conf.c_str())
                return false;
            }

            std::string line;
            while (std::getline(in, line))
            {
                std::vector<std::string> tokens;
                StringUtil::Split(line, ":", &tokens);
                if (tokens.size() != 2)
                {
                    // ns_log::LOG(WARNING) << "加载部分服务主机失败，请检查配置文件格式" << "\n";
                    ELOG("加载部分服务主机失败,请检查配置文件格式");
                    continue;
                }

                // 构建主机
                MachinePtr mp = std::make_shared<Machine>(tokens[0], std::stoi(tokens[1]));
                online.push_back(_machines.size());
                _machines.push_back(mp);
            }

            in.close();
            return true;
        }

    public:
        bool SmartChoice(int *id, MachinePtr *m)
        {
            {
                // 获取在线主机数量
                std::unique_lock<std::mutex> lock(_mutex);
                int online_num = online.size();
                if (online_num == 0)
                {
                    // ns_log::LOG(FATAL) << "后端编译主机已经全部离线!!!, 请尽快修复" << "\n";
                    ELOG("后端编译主机已经全部离线!!!, 请尽快修复");
                    return false;
                }

                // 遍历在线主机,寻找负载最低的
                uint64_t min_load = _machines[online[0]]->Load();
                int min_index = online[0]; // 存放最小负载主机的下标
                for (int i = 1; i < online_num; i++)
                {
                    if (min_load > _machines[i]->Load())
                    {
                        min_load = _machines[i]->Load();
                        min_index = online[i];
                    }
                }

                // 将最小负载主机的ID和对象返回给外界
                *id = min_index;
                *m = _machines[min_index];
                return true;
            }
        }

        // 指定主机离线
        void OfflineMachine(int id)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            for (auto iter = online.begin(); iter != online.end(); ++iter)
            {
                if (*iter == id)
                {
                    // 找到要离线的主机了
                    offline.push_back(id);
                    online.erase(iter);
                    break;
                }
            }
        }

        // 全部主机上线
        void onlineMachines()
        {
            std::unique_lock<std::mutex> lock(_mutex);

            if (online.size() != 0)
            {
                // LOG(WARNING) << "当前还有主机在线!!!" << '\n';
                ELOG("当前还有主机在线!!!");
                return;
            }

            // 所有主机全部离线
            std::swap(online, offline);
            // LOG(INFO) << "所有的主机上线啦" << '\n';
            ILOG("所有的主机上线啦");
        }
    };

    // 消息队列发布者
    class PublishMQ
    {
    public:
        static PublishMQ *instance; // 单例的指针
        ~PublishMQ() {}

        static PublishMQ *getInstance()
        {
            if (instance == nullptr)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                if (instance == nullptr)
                {
                    instance = new PublishMQ();
                }
            }

            return instance;
        }

        // 发布消息至消息队列
        void publishMessage(const std::string &id, const std::string &compile_json, std::string *out_string)
        {
            // 组织消息，发送至交换机
            mq::BasicProperties bp;
            bp.set_id(mq::UUIDHelper::uuid());
            bp.set_delivery_mode(mq::DeliveryMode::DURABLE);
            bp.set_routing_key("queue1");
            channel->basicPublish("exchange1", &bp,compile_json);

            // 从redis中等待结果
            waitForResult(id, out_string);
        }

        void waitForResult(const std::string id, std::string *out_string)
        {
            // 创建redis对象
            sw::redis::Redis redis("tcp://127.0.0.1:6379");

            // 从队列中获取结果,超时时间设为8秒
            auto result = redis.blpop(id, std::chrono::seconds(8));
            if (result)
            {
                // 返回结果
                *out_string = result.value().second;
            }
            else
            {
                // std::cout << "Timeout or error occurred" << std::endl;
                ELOG("从redis中key为 %s 获取结果超时", id.c_str());
            }
        }

    private:
        PublishMQ()
        {
            init();
        }

        // 初始化
        void init()
        {
            // 1.定义异步工作线程
            worker = std::make_shared<mq::AsyncWorker>();
            // 2.创建连接
            conn = std::make_shared<mq::Connection>("127.0.0.1", 8888, worker);
            // 3.创建信道
            channel = conn->openChannel();
            // 4. 通过信道提供的服务完成所需
            // 1.创建一个交换机
            google::protobuf::Map<std::string, std::string> args;
            channel->declareExchange("exchange1", mq::ExchangeType::FANOUT, true, false, args);
            // 2.创建队列1
            channel->declareQueue("queue1", false, true, false, args);
            // 4.绑定队列
            channel->queueBind("exchange1", "queue1", "queue1"); // binding_key为queue1
        }

        // 防止拷贝构造和赋值操作
        PublishMQ(const PublishMQ &) = delete;
        PublishMQ &operator=(const PublishMQ &) = delete;

    private:
        // 1.定义异步工作线程
        mq::AsyncWorker::ptr worker;
        // 2.创建连接
        mq::Connection::ptr conn;
        // 3.创建信道
        mq::Channel::ptr channel;
        // 单例模式的锁
        static std::mutex _mutex;
    };
    // 类外定义
    PublishMQ *PublishMQ::instance = nullptr;
    std::mutex PublishMQ::_mutex;

    // 这里核心业务功能
    class Control
    {
    private:
        Model _model; // 提供后台数据
        View _view;   // 提供html渲染功能
        // LoadBlance _load_blance; // 核心负载均衡器
    public:
        Control() {}
        ~Control() {}

    public:
        // 上线所有主机,只有当主机全部离线才可以使用
        void RecoveryMachine()
        {
            // _load_blance.onlineMachines();
        }

        bool AllQuestions(std::string *html)
        {
            bool ret = true;
            std::vector<Question> questions;

            if (_model.GetAllQuestions(&questions))
            {
                // 按照题号排升序
                sort(questions.begin(), questions.end(), [](const Question &q1, const Question &q2)
                     { return q1.number < q2.number; });
                // 获取题目信息成功，将所有的题目数据构建成网页
                _view.AllExpandHtml(questions, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                ret = false;
            }

            return ret;
        }
        bool OneQuestion(const std::string &number, std::string *html)
        {
            bool ret = true;
            Question q;
            if (_model.GetOneQuestions(number, &q))
            {
                // 获取指定题目信息成功，将指定题目数据构建成网页
                _view.OneExpandHtml(q, html);
            }
            else
            {
                *html = "指定题目: " + number + " 不存在!";
                ret = false;
            }

            return ret;
        }

        void Judge(const std::string number, const std::string in_json, std::string *out_string)
        {
            // 0.根据题目编号，获取指定题目
            Question q;
            if (!_model.GetOneQuestions(number, &q))
            {
                return;
            }

            // 1.对in_json反序列化，获取用户提交代码
            Json::Value value;
            Json::Reader reader;
            reader.parse(in_json, value);
            std::string code = value["code"].asString();

            // 2.将测试用例代码和用户提交的代码拼接
            std::string id = "numbers:" +  mq::UUIDHelper::uuid(); // 将numbers:UUID作为redis的key
            Json::Value compile_value;
            compile_value["id"] = id;
            compile_value["code"] = code + '\n' + q.tail;
            compile_value["input"] = value["input"].asString();
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_value);

            // 3.将Json数据发布到消息队列中
            PublishMQ::getInstance()->publishMessage(id, compile_string, out_string);
        }
    };
}